package fr.vahren.sriksal.controller.impl;

import java.util.List;
import java.util.Map;

import com.opensymphony.xwork2.ActionContext;

import fr.vahren.sriksal.business.IMapBusiness;
import fr.vahren.sriksal.business.IPlayerBusiness;
import fr.vahren.sriksal.business.exception.InternalErrorException;
import fr.vahren.sriksal.controller.IMapController;
import fr.vahren.sriksal.controller.SriksalController;
import fr.vahren.sriksal.controller.bean.MapTile;
import fr.vahren.sriksal.controller.bean.MapTile.Biome;
import fr.vahren.sriksal.controller.bean.MapTile.Elevation;
import fr.vahren.sriksal.controller.bean.UserInfo;

public class MapController extends SriksalController implements IMapController {

	/**
	 * 
	 */
	private static final long serialVersionUID = -6306190753425879121L;
	public static final int MAP_W = 10;
	public static final int MAP_H = 10;

	// STRUTS PARAMS
	private MapTile[][] map;
	private List<Long[]> maps;

	private int x;
	private int y;
	private long mapId;

	private IMapBusiness mapBusiness;
	private IPlayerBusiness playerBusiness;

	@Override
	public String retrieveMapJSON() throws InternalErrorException {
		// get map
		map = mapBusiness.getMapByMapId(mapId);

		if (map == null) {
			setErrorMessage("Erreur : Aucune map avec l'id = " + mapId);
			return ERROR;
		} else {
			return SUCCESS;
		}

	}

	@Override
	public String addTown() throws InternalErrorException {
		if (!map[x][y].isHasTown()) {
			mapBusiness.addTown(map[x][y].getId());
		}
		return SUCCESS;
	}

	@Override
	public String retrieveMapList() throws InternalErrorException {
		maps = mapBusiness.getMapList(getUser().getId());
		return SUCCESS;
	}

	@Override
	public String showMapFromList() throws InternalErrorException {
		// nothing to do. Just showing map with mapId.
		return SUCCESS;
	}

	@Override
	public String retrieveAvailableMapList() throws InternalErrorException {
		maps = mapBusiness.getAvailableMapList(getUser().getId());
		return SUCCESS;
	}

	@Override
	public String chooseMap() {
		// Associate the player
		playerBusiness.associateMapWithPlayer(mapId, getUser().getId());
		return SUCCESS;
	}

	@Override
	public String createMap() throws InternalErrorException {
		// build the map
		buildMap();

		// save it
		saveMap(getUser().getId());

		return SUCCESS;
	}

	private void saveMap(long userId) {
		// Save the map
		mapId = mapBusiness.saveMap(map, userId);

		// Save the association Player/Map
		playerBusiness.associateMapWithPlayer(mapId, userId);
	}

	private void buildMap() {
		map = new MapTile[MAP_W][MAP_H];
		Biome randomBiome;
		Elevation randomElevation;
		for (int y = 0; y < MAP_H; y++) {
			for (int x = 0; x < MAP_W; x++) {
				int[] neighbourBiomeDistribution = getNeighbourBiomeDistribution(
						x, y);
				int[] neighbourElevationDistribution = getNeighbourElevationDistribution(
						x, y);
				randomBiome = Biome.values()[getRandomDistributedValue(neighbourBiomeDistribution)];
				randomElevation = Elevation.values()[getRandomDistributedValue(neighbourElevationDistribution)];
				map[x][y] = new MapTile(randomElevation, randomBiome, false, x
						* MAP_W + y);
			}
		}
	}

	private int[] getNeighbourElevationDistribution(int x, int y) {
		int[] distrib = new int[Elevation.values().length];
		for (int i = 0; i < distrib.length; i++) {
			distrib[i] = 1;
		}

		int elevI;
		// Voisin de gauche
		if (x - 1 >= 0) {
			elevI = map[x - 1][y].getElevation().ordinal();
			distrib[elevI] = distrib[elevI] + 2;
		}
		// Voisin du haut
		if (y - 1 >= 0) {
			elevI = map[x][y - 1].getElevation().ordinal();
			distrib[elevI] = distrib[elevI] + 2;
		}
		return distrib;
	}

	private int[] getNeighbourBiomeDistribution(int x, int y) {
		int[] distrib = new int[Biome.values().length];
		for (int i = 0; i < distrib.length; i++) {
			distrib[i] = 1;
		}

		int biomeI;
		// Voisin de gauche
		if (x - 1 >= 0) {
			biomeI = map[x - 1][y].getElevation().ordinal();
			distrib[biomeI] = distrib[biomeI] + 2;
		}
		// Voisin du haut
		if (y - 1 >= 0) {
			biomeI = map[x][y - 1].getElevation().ordinal();
			distrib[biomeI] = distrib[biomeI] + 2;
		}
		return distrib;
	}

	private UserInfo getUser() {
		Map<String, Object> session = ActionContext.getContext().getSession();
		UserInfo user = (UserInfo) session.get(AuthController.S_USER);
		return user;
	}

	public int getRandomDistributedValue(int[] distribution) {
		int total = 0;
		for (int v : distribution) {
			total += v;
		}
		int random = (int) Math.floor((Math.random() * total));
		int cumul = distribution[0];
		for (int i = 0; i < distribution.length - 1; i++) {
			if (random >= cumul) {
				cumul += distribution[i + 1];
			} else {
				return i;
			}
		}
		return distribution.length - 1;
	}

	/**
	 * @param mapBusiness
	 *            the mapBusiness to set
	 */
	public void setMapBusiness(IMapBusiness mapBusiness) {
		this.mapBusiness = mapBusiness;
	}

	/**
	 * @return the map
	 */
	public MapTile[][] getMap() {
		return map;
	}

	/**
	 * @return the x
	 */
	public int getX() {
		return x;
	}

	/**
	 * @param x
	 *            the x to set
	 */
	public void setX(int x) {
		this.x = x;
	}

	/**
	 * @return the y
	 */
	public int getY() {
		return y;
	}

	/**
	 * @param y
	 *            the y to set
	 */
	public void setY(int y) {
		this.y = y;
	}

	/**
	 * @return the maps
	 */
	public List<Long[]> getMaps() {
		return maps;
	}

	/**
	 * @return the mapId
	 */
	public long getMapId() {
		return mapId;
	}

	/**
	 * @param mapId
	 *            the mapId to set
	 */
	public void setMapId(long mapId) {
		this.mapId = mapId;
	}

	/**
	 * @param playerBusiness
	 *            the playerBusiness to set
	 */
	public void setPlayerBusiness(IPlayerBusiness playerBusiness) {
		this.playerBusiness = playerBusiness;
	}

}
